package main

import (
	"archive/tar"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"

	"strings"
)

type FileInfo struct {
	IsDir     bool
	Size      int64
	Name      string
	UpdatedAt int64
}

type ProjectFileInfo struct {
	Path      string
	FileMaps  map[string]FileInfo
	FileLimit int64
	DirLimit  int64
	Err       error
}

// 检测 是否需要过滤
func checkFilter(file string, isDir bool, filterStrs []string) bool {
	// 过滤 node_modules文件夹 且过滤 dist文件夹 数组写法
	for _, filterStr := range filterStrs {
		// 判断 fileterStr 是否有 *
		if !isDir && filterStr[len(filterStr)-1:] == "*" {
			// 过滤掉包含 *后的文件名
			if len(file) >= len(filterStr)-1 && file[:len(filterStr)-1] == filterStr[:len(filterStr)-1] {
				return true
			}
		} else if file == filterStr || file+"/" == filterStr {
			return true
		}
	}
	return false
}

// 递归获取指定目录下的所有文件名
func GetAllFile(src string, filterStrs []string) (fileMaps map[string]FileInfo, fileLimit int64, dirLimit int64, err error) {
	fileMaps = make(map[string]FileInfo)
	// 遍历源文件夹
	filepath.Walk(src, func(file string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		isDir := info.IsDir()
		srcLen := len(src)

		// 检测是否需要过滤
		checkFilterFlag := checkFilter(info.Name(), isDir, filterStrs)

		// 如果是文件夹则过滤文件夹 否则过滤文件
		if checkFilterFlag {
			if isDir {
				return filepath.SkipDir
			}
			return nil
		}

		if isDir {
			dirLimit++
		} else {
			fileLimit++
		}

		// 获取文件的header信息
		header, err := tar.FileInfoHeader(info, info.Name())
		if err != nil {
			return err
		}

		// 设置header的name为相对路径，以便保持目录结构
		header.Name = filepath.ToSlash(file)
		// 去除前置路径
		header.Name = header.Name[srcLen:]

		fileMaps[header.Name] = FileInfo{
			Name:      header.Name,
			IsDir:     isDir,
			Size:      info.Size(),
			UpdatedAt: info.ModTime().Unix(),
		}
		return nil
	})
	return
}

func GetDir(path string) (projectInfo ProjectFileInfo) {
	// iotService
	// path := "D:/code/work_code/iot/iotService"
	// .gitignore
	fmt.Println("path=", path)
	bytes, err := ioutil.ReadFile(path + "/.gitignore")
	if err != nil {
		fmt.Println("读取文件失败，err=", err)
	}

	// 过滤文件 去除空格
	var results []string
	splits := strings.Split(string(bytes), "\n")
	for i, line := range splits {
		splits[i] = strings.TrimSpace(line)
		if splits[i] != "" {
			results = append(results, splits[i])
		}
	}
	results = append(results, []string{".git", ".gitignore"}...)
	fmt.Println("过滤文件：", results)
	// var files []string
	projectInfo.FileMaps, projectInfo.FileLimit, projectInfo.DirLimit, projectInfo.Err = GetAllFile(path, results)
	fmt.Println("文件夹:", projectInfo.DirLimit, "文件:", projectInfo.FileLimit, "总数:", len(projectInfo.FileMaps))

	// fmt.Println("目录下的所有文件如下:")
	// for i := 0; i < len(fileList); i++ {
	// 	fmt.Println(fileList[i].Name, fileList[i].IsDir)
	// }
	return
}

// 比较两个项目的区别
func CheckPath(projectInfoA ProjectFileInfo, projectInfoB ProjectFileInfo) (addFileA []FileInfo, addFileB []FileInfo, changeFile []FileInfo) {

	// 遍历两个项目文件夹的差异  a项目有文件 b项目没有文件 或 a项目和 b项目文件不一致
	for key, fileInfoA := range projectInfoA.FileMaps {
		if _, ok := projectInfoB.FileMaps[key]; !ok {
			// 新增文件
			// fmt.Println("新增文件:", key)
			addFileA = append(addFileA, fileInfoA)
			continue
		}
		fileInfoB := projectInfoB.FileMaps[key]
		if fileInfoA.Size != fileInfoB.Size {
			// 大小或修改时间有变化
			// fmt.Println("文件:", key, "大小 有变化")
			changeFile = append(changeFile, fileInfoA)
		}
	}

	// b项目有文件
	for _, fileInfoB := range projectInfoB.FileMaps {
		if _, ok := projectInfoA.FileMaps[fileInfoB.Name]; !ok {
			addFileB = append(addFileB, fileInfoB)
			continue
		}
	}

	return
}

type CheckProjectInfoType struct {
	ProjectA ProjectFileInfo
	ProjectB ProjectFileInfo

	AddFileA   []FileInfo
	AddFileB   []FileInfo
	ChangeFile []FileInfo
}

func CheckProject(pathA string, pathB string) (checkInfo CheckProjectInfoType) {
	checkInfo.ProjectA = GetDir(pathA)

	checkInfo.ProjectB = GetDir(pathB)

	checkInfo.AddFileA, checkInfo.AddFileB, checkInfo.ChangeFile = CheckPath(checkInfo.ProjectA, checkInfo.ProjectB)
	fmt.Println("项目A:", len(checkInfo.AddFileA), "项目B:", len(checkInfo.AddFileB), "修改文件:", len(checkInfo.ChangeFile))
	return
}

// ensureDirExists 检查指定路径的上级目录是否存在，如果不存在则创建它们
func ensureDirExists(filePath string) error {
	dirPath := filepath.Dir(filePath)
	// 使用 Stat 检查目录是否存在
	if _, err := os.Stat(dirPath); os.IsNotExist(err) {
		// 目录不存在，使用 MkdirAll 创建所有上级目录
		if err := os.MkdirAll(dirPath, 0755); err != nil {
			return fmt.Errorf("failed to create directory: %w", err)
		}
		fmt.Println("Directory created:", dirPath)
	} else if err != nil {
		// 其他错误
		return fmt.Errorf("failed to check directory: %w", err)
	}
	return nil
}

// 同步文件
func AsyncFile(pathA string, pathB string, fileStr string) {
	fileList := strings.Split(fileStr, ",")
	for _, file := range fileList {
		fmt.Println(file)
		if file == "" {
			continue
		}

		fileInfo, err := os.Stat(pathA + file)
		if err != nil {
			fmt.Println("Error:", err)
			continue
		}

		isDir := fileInfo.IsDir()
		if isDir {
			// 使用 MkdirAll 创建文件夹，包括所有父目录
			err = os.MkdirAll(pathB+file, 0755)
			if err != nil {
				fmt.Println("Error creating directory:", err)
				continue
			}
			fmt.Println("Directory created with MkdirAll")
		} else {
			// 打开A 文件
			fileA, err := os.Open(pathA + file)
			if err != nil {
				fmt.Println("打开A文件失败，err=", err)
				continue
			}
			defer fileA.Close()

			if err := ensureDirExists(pathB + file); err != nil {
				fmt.Println("Error:", err)
			}

			// 打开B 文件 os.O_CREATE|os.O_WRONLY, 0644
			// 使用 Stat 检查文件是否存在
			if _, err := os.Stat(pathB + file); err == nil {
				// 文件存在，使用 Remove 删除它
				if err := os.Remove(pathB + file); err != nil {
					// return fmt.Errorf("failed to remove file: %w", err)
					fmt.Printf("failed to remove file: %v", err)
					continue
				}
				fmt.Println("File removed:", pathB+file)
			}
			fileB, err := os.Create(pathB + file)
			if err != nil {
				fmt.Println("打开B文件失败，err=", err)
				continue
			}
			defer fileB.Close()

			// 将文件A的内容复制到文件B
			_, err = io.Copy(fileB, fileA)
			if err != nil {
				fmt.Println("复制文件内容失败，err=", err, "文件名:", fileB, "文件大小:", fileA)
				// log.Fatalf("复制文件内容失败: %v", err)
			}
		}
	}
}

// func main() {
/*
		// 1 检查 文件夹差异
	pathA := "D:/code/work_code/iot/iotService"
	projectInfoA := GetDir(pathA)

	pathB := "D:/code/work_code/yanglaoyuan/serverApi"
	projectInfoB :=GetDir(pathB)

	addFileA, addFileB, changeFile := CheckPath(projectInfoA, projectInfoB)
	fmt.Println("项目A:", len(addFileA), "项目B:", len(addFileB), "修改文件:", len(changeFile))
*/

/*
	// 2 同步选中的文件
	fileStrs := "/app/model/iot/product_model_group.js,/app/controller/deviceShareAuth/deviceAuth.js,/app/middleware/systemAuth.js,/app/service/iot/product.js"
	pathA := "D:/code/work_code/iot/iotService"
	pathB := "D:/code/work_code/yanglaoyuan/serverApi"
	AsyncFile(pathA, pathB, fileStrs)
*/

/*
		// 3 读取文件内容

	fileName := "2.txt"
	data, err := ioutil.ReadFile(fileName)
	if err != nil {
		log.Panicf("failed reading data from file: %s", err)
	}
	fmt.Printf("\n%s", data)
	fmt.Printf("\n%s", string(data))
*/
// }
